/* ********************************************************************************************
*  Alvin Baldemeca
*  Tetris Project
*  Course: TCSS 305 , Alan Fowler
*  Term: Spring 2012
*  UNIVERISTY OF WASHINGTON TACOMA
*
*
* The methods getColor() and getNewColor() is taken from sample code given for timer on 
* UWT TCSS 305A moodle class site written by Alan Fowler. 
*
**********************************************************************************************/


package view;

import controller.Board;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;

import javax.swing.JPanel;

import model.AbstractPiece;



/**
 * This class contains the graphical representation of the board and its
 * current state as pieces are moving and rotating.
 * @author Alvin Baldemeca
 * @version 5/24/2012
 *
 */
@SuppressWarnings("serial")
public class TetrisBoardPanel extends JPanel implements Observer
{
  
  /**
   * The width of the frame when it is initialized.
   */
  private static final int WIDTH = 400;
  
  /**
   * The height of the frame when it is initialized.
   */
  private static final int HEIGHT = 475;
  
  /**
   * The change in color of my_color that is used by the getNewColor() method.
   */
  private static final int MAX_CHANGE = 15;

  /**
   * The maximum color value.
   */
  private static final int MAX_COLOR = 255;
  
  /**
   * The roundness angle used for the rectangle display of next piece and swap piece.
   */
  private static final int ANGLE = 45;
  
  /**
   * The offset used to set the initial message.
   */
  private static final int FONT_WIDTH_OFFSET = 40;
  
  /**
   * The size of piece panel in proportion to the size of one block of the  tetris board.
   */
  private static final int SIZE_OF_PIECE_PANEL = 5;
  
  /**
   * The number of lines to clear to get to the next level.
   */
  private static final int LINES_TO_GET_TO_NEXT_LEVEL = 20;
  
  /**
   * Padding needed to compensate for the next piece and swap piece panel.
   */
  private static final int WIDTH_OFFSET_FOR_PANEL = 7;
  
  /**
   * The borded stroke used to paint the boarder.
   */
  private static final int BORDER_STROKE = 3;
  
  /**
   * The size offset needed to paint a short message.
   */
  private static final int SHORT_MESSAGE_SIZE = 3;
  
  /**
   * The state if the game is have just been started.  Returns true if it is 
   * at its initial state, false otherwise.
   */
  private boolean my_initial_message = true;
  
  /**
   * A reference to the tetris board for which this board is needs to 
   * represent graphically.   
   */
  private final Board my_board;
  

  /**
   * The random number generator to use for colors.
   */
  private final Random my_random = new Random();

  /**
   * The direction in which we are changing the red value.
   */
  private int my_red_dir = 1;

  /**
   * The direction in which we are changing the blue value.
   */
  private int my_blue_dir = -1;

  /**
   * The direction in which we are changing the green value.
   */
  private int my_green_dir = 1;
  
  /**
   * The color used for the board background used for a cool mode graphics. 
   */
  private Color my_color; 
  
  /**
   * States where the modes a dizzy mode for the board.
   */
  private boolean my_dizzy_mode;
  
  /**
   * Staes whether the grid is on.
   */
  private boolean my_grid_on;
  
  /**
   * List of all the board painted.
   */
  private final List<Color[][]> my_board_list;
  
  /**
   * States wheter the game is paused or not.
   */
  private boolean my_paused;
  
  /**
   * A list of string containing the instruction of how to used the control
   * that is displayed for the user or game player.
   */
  private final String[] my_instructions = new String[]{
    "Select New Game to start.",
    "-------------------------",
    "To move the pieces use :",
    "Left arrow  : move left ",
    "Right arrow : move right",
    "Down arrow  : move down ",
    "UP arrow    : rotates   ",
    "Spacebar    : drop down ",
    "S key       : Swaps     ",
    "Backspace   : Undo      "
    
    
    
    
    
    
    
    
  };

  /**
   * A constructor for the TetrisBoardPanel.
   * @param the_board the tetris board that panel needs to represent.
   */
  public TetrisBoardPanel(final Board the_board)
  {
    super();
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    my_paused = false;
    setBackground(Color.GRAY);
    my_board = the_board;
    my_color = Color.WHITE;
    my_dizzy_mode = false;
    my_grid_on = false;
    my_board_list = new ArrayList<Color[][]>();
  }
  
  /**
   * Gets the state of my_paused.
   * @return the state that my_paused is set to.
   */
  public boolean isPause()
  {
    return my_paused;
  }
  
  /**
   * States whether the game is paused or not.
   * @param the_state whether the game is paused or not.
   */
  public void setPause(final boolean the_state)
  {
    my_paused = the_state;
  }
  /**
   * Returns true if t he tetris bord grid is turned on.
   * @return whether or not the grid is turned on or is painted on the board.
   */
  public boolean isGridOn()
  {
    return my_grid_on;
  }
  
  /**
   * Sets whether the grid should be painted.
   * @param the_grid true or false whether the grid should be painted.
   */
  public void setGrid(final boolean the_grid)
  {
    my_grid_on = the_grid;
  }
  
  /**
   * States whether the dizzy mode is on.
   * @return wheter or not dizzy mode is on
   */
  public boolean isDizzyOn()
  {
    return my_dizzy_mode;
  }
  
  /**
   * Sets the dizzy mode.
   * @param the_dizzy if true dizzy mode is on, else its off.
   */
  public void setDizzy(final boolean the_dizzy)
  {
    my_dizzy_mode = the_dizzy;
  }
  @Override
  public void paintComponent(final Graphics the_graphics) 
  {
    super.paintComponent(the_graphics);
    final Graphics2D g2 = (Graphics2D) the_graphics;
    
    paintTetrisBoardPanel(g2);
  }

  /**
   * Paints the tetris whole board panel on the Graphics2D which includes the board, the next
   * piece, the swapped piece and anyother messegase to the user.
   * @param the_graphics2d the Graphics2D to paint the board to.
   */
  private void paintTetrisBoardPanel(final Graphics2D the_graphics2d)
  {    
    
    final int boardheight = my_board.getContainer().length;
    final int boardwidth = my_board.getContainer()[0].length;
    int aspect;
    if (boardwidth >= boardheight)
    {
      aspect = boardwidth + WIDTH_OFFSET_FOR_PANEL;
    }
    else
    {
      aspect = boardheight + 2;
    }
    
    
    final Rectangle rect = the_graphics2d.getClipBounds();
    int size = rect.height;
    if (rect.width < rect.height)
    {
      size = rect.width / aspect;
    }
    else
    {
      size = rect.height / aspect;
    }
    final int offset = size;
    paintTetrisBoard(the_graphics2d, rect, offset);
    if (my_grid_on)
    {
      paintBoardGrid(the_graphics2d, offset);
    }
    
    if (my_board.isGameOver())
    {
      paintShortMessage(the_graphics2d, offset, "GAME OVER", Color.GREEN);
      paintScore(the_graphics2d, offset);
    }
    else
    {
      setBackground(Color.GRAY);
      paintBoardBorder(the_graphics2d, offset, Color.GREEN);  
      paintNextPiece(the_graphics2d, offset);
      paintSwapPiece(the_graphics2d, offset);
      paintScore(the_graphics2d, offset);
    }
    
    if (isPause())
    {
      paintShortMessage(the_graphics2d, offset, "... PAUSE ...", Color.GREEN);
    }
    
    if (my_initial_message)
    {
      paintInitialMessage(the_graphics2d, offset);
    }
  
  }
  
  
  /**
   * This method paints the tetris board in its current state.
   * @param the_graphics2d the Graphics2D object to paint the tetris board to.
   * @param the_rect the rectangle dimension of the current frame.
   * @param the_offset the offset to paint the tetris board
   */
  private void paintTetrisBoard(final Graphics2D the_graphics2d, final Rectangle the_rect,
                                final int the_offset)
  {
    final int boardheight = my_board.getContainer().length;
    final int boardwidth = my_board.getContainer()[0].length;
    my_board.freezePiece(true);
    the_graphics2d.setColor(getBackground());
    the_graphics2d.fillRect(the_rect.x, the_rect.y, the_rect.width, the_rect.height);
    
    the_graphics2d.setColor(Color.BLACK);
    the_graphics2d.fillRect(the_offset, 2 * the_offset, the_offset * 
                            boardwidth, the_offset * boardheight);

    final Color[][] copy = my_board.getContainer(); 
    for (int i = 0; i < boardheight; i++)
    {
      for (int j = 0; j < boardwidth; j++)
      {
       
        if (copy[i][j] == null &&  my_dizzy_mode)
        {  
          the_graphics2d.setColor(getColor());
          the_graphics2d.fill3DRect((j + 1) * the_offset, (i + 2) * the_offset , 
                                    the_offset, the_offset, true);
          
        }
        else if (copy[i][j] != null)
        {
          the_graphics2d.setBackground(Color.BLACK);
          the_graphics2d.setColor(copy[i][j]);
          the_graphics2d.fill3DRect((j + 1) * the_offset, 
                                    (i + 2) * the_offset, 
                                    the_offset, 
                                    the_offset, 
                                    true);
        }
      }
    }
    my_board.freezePiece(false);
    


  }
  
  /**
   * Paints the initial message when the before a game starts.
   * @param the_graphics2d the Grapics2D object to paint with
   * @param the_offset the offset used for the size of the font.
   */
  private void paintInitialMessage(final Graphics2D the_graphics2d, final int the_offset)
  {       

    
    for (int i = 0; i < my_instructions.length; i++)
    {
      paintMessage(the_graphics2d, my_instructions[i], i * the_offset, Color.WHITE);
    }

  }
  
  /**
   * Paints a short message for the paint component.
   * @param the_graphics2d the Graphics2D to paint with
   * @param the_offset the offset needed for the message.
   * @param the_message the message to paint.
   * @param the_color the color of the message to paint.
   */
  private void paintShortMessage(final Graphics2D the_graphics2d, final int the_offset, 
                                 final String the_message, final Color the_color)
  {
    final int y_message = my_board.getContainer().length / 2;
    this.setBackground(getColor()); 
    the_graphics2d.setColor(the_color);
    the_graphics2d.setFont(new Font("   SansSerif   ", Font.BOLD, 
                                    the_offset * SHORT_MESSAGE_SIZE / 2));
    the_graphics2d.drawString(the_message, the_offset, the_offset * y_message); 
  }
  /**
   * This method paints the boarder of the board.
   * @param the_graphics2d the Graphics2D to paint with
   * @param the_offset the offset for the spacing.
   * @param the_color the color to paint the boarder with.
   */
  private void paintBoardBorder(final Graphics2D the_graphics2d, 
                                  final int the_offset,
                                  final Color the_color)
  {
    final int boardheight = my_board.getContainer().length;
    final int boardwidth = my_board.getContainer()[0].length;
    the_graphics2d.setColor(the_color);
    the_graphics2d.setStroke(new BasicStroke(BORDER_STROKE));
    the_graphics2d.drawRect(the_offset, 2 * the_offset, (the_offset * boardwidth) + 2,
                            (the_offset * boardheight) + 2);

  }
  
  
  /**
   * This method paint the next to be dropped on the board. 
   * @param the_graphics2d the graphics to paint the next piece to
   * @param the_offset the offset used to paint the size and spacing.
   */
  private void paintNextPiece(final Graphics2D the_graphics2d, final int the_offset)
  {

    final int aspect = my_board.getContainer()[0].length + 2;
    final int aspecty = my_board.getContainer().length / WIDTH_OFFSET_FOR_PANEL;


    
    the_graphics2d.setColor(Color.BLACK);
    the_graphics2d.fillRoundRect(the_offset * aspect, the_offset * aspecty, 
                                 the_offset * SIZE_OF_PIECE_PANEL, 
                                 the_offset * SIZE_OF_PIECE_PANEL,
                                 ANGLE, ANGLE);
 
    if (!my_initial_message)
    {
      final AbstractPiece next_piece = (AbstractPiece) my_board.getNextPiece();
      final int[][] tetris_piece = next_piece.getRotation();
      
      for (int i = 0; i < tetris_piece.length; i++)
      {
        the_graphics2d.setColor(next_piece.getColor());
        the_graphics2d.fill3DRect((tetris_piece[i][0] + aspect + 1) * the_offset, 
                               (2 + aspecty - tetris_piece[i][1]) * the_offset , 
                               the_offset, the_offset, true);
      }
    }
    
    

    
    the_graphics2d.setColor(Color.BLUE);
    the_graphics2d.setFont(new Font(" SansSerif ", Font.BOLD, the_offset));
    the_graphics2d.drawString("NEXT", the_offset  * (aspect + 1), (the_offset * aspecty) - 1);
    
    
  }
  
  /**
   * Paints the level and score of the game.
   * @param the_graphics2d the Graphics2D to paint the object with.
   * @param the_offset the spacing used to place the level and score on the graphics.
   */
  private void paintScore(final Graphics2D the_graphics2d, final int the_offset)
  {
    final int score = my_board.getScore();
    final int level = my_board.getNumberOfLinesCleared() / LINES_TO_GET_TO_NEXT_LEVEL;
    the_graphics2d.setColor(Color.GREEN);
    the_graphics2d.setFont(new Font("SansSerif   ", Font.BOLD, the_offset - 1));
    the_graphics2d.drawString("Level " + level +
                              ".       Score : " + score, the_offset, the_offset);
  }
  
  
  /**
   * This method paint the next to be dropped on the board.  
   * @param the_graphics2d the Graphics2D to paint the swap piece with.
   * @param the_offset the spacing used to place the next piece in the proper place.
   */
  private void paintSwapPiece(final Graphics2D the_graphics2d, final int the_offset)
  {
    final int aspect = my_board.getContainer()[0].length + 2;
    final int aspecty = 1 + my_board.getContainer().length / 2;

    the_graphics2d.setColor(Color.BLACK);
    the_graphics2d.fillRoundRect(the_offset * aspect, the_offset * aspecty, 
                                 the_offset * SIZE_OF_PIECE_PANEL, 
                                 the_offset * SIZE_OF_PIECE_PANEL ,
                                 ANGLE, ANGLE);
    
    if (my_board.getSwapPiece() != null)
    {
      final AbstractPiece next_piece = (AbstractPiece) my_board.getSwapPiece();
      final int[][] tetris_piece = next_piece.getRotation();
      
      for (int i = 0; i < tetris_piece.length; i++)
      {
        the_graphics2d.setColor(next_piece.getColor());
        the_graphics2d.fill3DRect((tetris_piece[i][0] + aspect + 1) * the_offset, 
                               (2 + aspecty - tetris_piece[i][1]) * the_offset , 
                               the_offset, the_offset, true);
      }
    }
    
    
    the_graphics2d.setColor(Color.BLUE);
    the_graphics2d.setFont(new Font("SansSerif ", Font.BOLD, the_offset));
    the_graphics2d.drawString("SWAP", the_offset  * (aspect + 1), (the_offset * aspecty) - 1);
  }
  
  
 /**
  * This method paints the message to be dispalyed in the provided Graphics2D object.
  * @param the_graphics2d the Grphics2D object to paint the message to.
  * @param the_message the string message to paint.
  * @param the_y the height offset to paint the message to.
  * @param the_color the color to paint the message.
  */
  private void paintMessage(final Graphics2D the_graphics2d, 
                            final String the_message, final int the_y, final Color the_color) 
  {
    final Rectangle rect = the_graphics2d.getClipBounds();
    int size = rect.height;
    if (rect.width < rect.height)
    {
      size = rect.width;
    }
    else
    {
      size = rect.height;
    }
    
    final int offset = size / my_board.getContainer().length;
    final int defaultfontsize = (2 * size) / my_board.getContainer()[0].length;
    
    final int fontwidth = size / FONT_WIDTH_OFFSET;
    the_graphics2d.setFont(new Font("SansSerif", Font.BOLD, fontwidth));
    the_graphics2d.setColor(the_color);
    the_graphics2d.drawString(the_message, offset,  the_y + defaultfontsize);
  
  }
  
  /**
   * This method paints the board grid.
   * @param the_graphics2d the Graphics2D to paint with.
   * @param the_offset the offset needed to resize the grids to.
   */
  private void paintBoardGrid(final Graphics2D the_graphics2d, final int the_offset)
  {
    
    final int boardheight = my_board.getContainer().length + 2;
    final int boardwidth = my_board.getContainer()[0].length + 1;
    the_graphics2d.setStroke(new BasicStroke(1));
    the_graphics2d.setColor(Color.GRAY);
    for (int i = 0; i <= boardwidth; i++) 
    {
      the_graphics2d.drawLine(i * the_offset, 0, i * the_offset, boardheight * the_offset);
    }

    for (int i = 0; i <= boardheight; i++) 
    {
      the_graphics2d.drawLine(0, i * the_offset, boardwidth * the_offset, i * the_offset);
    }
  }
  
  
  /*
   * Code taken from sample code given for timer on UWT TCSS 305A moodle class site
   * witten by Allan Fowler. 
   */
  /**
   * Gets the color variation when the for the next color using the current color.
   * @return the new color to use.
   */
  public Color getColor()
  {
    final int new_red = getNewColor(my_color.getRed(), my_red_dir);
    if (new_red == 0 || new_red == MAX_COLOR) 
    {
      my_red_dir = -my_red_dir;
    }
    final int new_green = getNewColor(my_color.getGreen(), my_green_dir);
    if (new_green == 0 || new_green == MAX_COLOR) 
    {
      my_green_dir = -my_green_dir;
    }
    final int new_blue = getNewColor(my_color.getBlue(), my_blue_dir);
    if (new_blue == 0 || new_blue == MAX_COLOR) 
    {
      my_blue_dir = -my_blue_dir;
    }
    my_color = new Color(new_red, new_green, new_blue);
    return my_color;

  }
  
  /*
   * Code taken from sample code given for timer on UWT TCSS 305A moodle class site
   * witten by Allan Fowler. 
   */
  /**
   * Used by the getColor() method to create a new color.
   * @param the_old the RGB value of the old color.
   * @param the_dir the direction to change the value to.
   * @return the new color created from the old color.
   */
  private int getNewColor(final int the_old, final int the_dir) 
  {
    int result = the_old + the_dir * my_random.nextInt(MAX_CHANGE);
    if (result > MAX_COLOR) 
    {
      result = MAX_COLOR;
    } 
    else if (result < 0) 
    {
      result = 0;
    }
    return result;
  }
  
  /**
   * Returns the array list of all the board state painted.
   * @return the list of all the board state in a 2 dimensional array of color
   */
  public List<Color[][]> getGraphics2D()
  {
    return my_board_list;
  }
  
  /**
   * If the parameter is true, set the game is in its initial state where the board 
   * is empty and game hasn't started.
   * @param the_start where is should be in its initial state or not
   */
  public void setInitialState(final boolean the_start)
  {
    my_initial_message = the_start;
  }
  
  /**
   * Returns true if the game is at its initial state.
   * @return true if the game is at its initial state.
   */
  public boolean isStateInitial()
  {
    return my_initial_message;
  }
  
  @Override
  public void update(final Observable the_first_argument, final Object the_second_argument)
  {
    repaint();
  }
  
  
}
